import org.apache.commons.math3.genetics.*;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;

/**
 * This is also an example of usage.
 */
public class GAProcess extends GeneticAlgorithm {
    // parameters for the GA
    private static final int DIMENSION = 64;
    private static final int POPULATION_SIZE = 200; //染色体数量
    private static final int NUM_GENERATIONS = 150; //迭代代数
    private static final double ELITISM_RATE = 0.1; //精英率，将某些最优基因不通过选择直接进入下一次进化
    private static final double CROSSOVER_RATE = 0.4; //交叉率
    private static final double MUTATION_RATE = 1; //突变率
    private static final int TOURNAMENT_ARITY = 2; //父代染色体数量
    private int generationsEvolved = 0;

    private static Random generator;
    private static IProblemSet problem;
    private static MathUtil mathUtil;

    public GAProcess(){
        super(new OnePointCrossover<Integer>(),
                CROSSOVER_RATE, // all selected chromosomes will be recombined (=crosssover)
                new BinaryMutation(),
                MUTATION_RATE,
                new TournamentSelection(TOURNAMENT_ARITY));

    }

    public void start() {
        mathUtil = new MathUtil(DIMENSION);
        problem = new ProblemSet();
        generator = new Random();
        // to test a stochastic algorithm is hard, so this will rather be an usage example

        // initialize a new genetic algorithm
//        GeneticAlgorithm ga = new GeneticAlgorithm(
//                new OnePointCrossover<Integer>(),
//                CROSSOVER_RATE, // all selected chromosomes will be recombined (=crosssover)
//                new BinaryMutation(),
//                MUTATION_RATE,
//                new TournamentSelection(TOURNAMENT_ARITY)
//        );

        // initial population
        Population initial = randomPopulation();
        // stopping conditions
        StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);

        // best initial chromosome
        Chromosome bestInitial = initial.getFittestChromosome();

        // run the algorithm
        Population finalPopulation = this.evolve(initial, stopCond);

        // best chromosome from the final population
        Chromosome bestFinal = finalPopulation.getFittestChromosome();
        System.out.println("Best Result:");
        System.out.println(bestFinal);

    }

    /**
     * Initializes a random population.
     */
    private static ElitisticListPopulation randomPopulation() {
        List<Chromosome> popList = new LinkedList<>();
        byte[][] s = new byte[2][];
        for (int i=0; i<POPULATION_SIZE; i++) {
            double[] loc = new double[2];
            for (int j = 0; j < 2; j++) {
                loc[j] = problem.getLOC_LOW() + generator.nextDouble() * (problem.getLOC_HIGH() - problem.getLOC_LOW());
            }
            AbstractListChromosome randChrom = new FindOnes(MathUtil.concat(loc[0],loc[1]));
//            System.out.println(randChrom);
            popList.add(randChrom);
        }
        return new ElitisticListPopulation(popList, popList.size(), ELITISM_RATE);
    }

    /**
     * Chromosomes represented by a binary chromosome.
     *
     * The goal is to set all bits (genes) to 1.
     */
    private static class FindOnes extends BinaryChromosome {
        public FindOnes(List<Integer> representation) {
            super(representation);
        }

        /**
         * Returns number of elements != 0
         */
        public double fitness() {
            double num;
            List<Integer> list = this.getRepresentation();
            double[] XY = MathUtil.divide(list);
            Location location = new Location(XY);
            num = problem.evaluate(location);
            return -num;
        }

        @Override
        public AbstractListChromosome<Integer> newFixedLengthChromosome(List<Integer> chromosomeRepresentation) {
            return new FindOnes(chromosomeRepresentation);
        }

        @Override
        public String toString(){
            List<Double> list = new ArrayList<>();
            double[] location = MathUtil.divide(this.getRepresentation());
            if(location == null){
                return "error";
            }
            if(location.length !=0) {
                list.add(location[0]);
                list.add(location[1]);
            }
            return String.format("f=%s %s", -this.getFitness(),list);
        }
    }

    @Override
    public Population evolve(Population initial, StoppingCondition condition) {
        Population current = initial;

        for(this.generationsEvolved = 0; !condition.isSatisfied(current); ++this.generationsEvolved) {
            current = this.nextGeneration(current);
            System.out.println(current.getFittestChromosome());
        }

        return current;
    }
}